/////////////////////////////////////////////////////////////////////////
//
// Amuse Engine SDK - core/container
// Copyright (c) 2014.  All Rights Reserved
//
// File:		AETBaseArray.inl
// Author:		Gianluca Belardelli
// Date:		16/04/2014
//
/////////////////////////////////////////////////////////////////////////
#ifndef _AETBASEARRAY_INL_
#define _AETBASEARRAY_INL_

// TODO
//#define AE_COMPUTE_OPTIMIZED_CAPACITY( size, numFreeElemsLeft, shrinkExact ) (shrinkExact) ? (size + numFreeElemsLeft) : hkNextPowerOf2(size + numFreeElemsLeft)
#define AE_COMPUTE_OPTIMIZED_CAPACITY( size, numFreeElemsLeft, shrinkExact ) (shrinkExact) ? (size + numFreeElemsLeft) : (size + numFreeElemsLeft)

template <typename T>
AE_FORCEINLINE AETBaseArray<T>::AETBaseArray( void ) : m_lpData( 0L ),
														m_nSize( 0 ),
														m_nCapacityAndFlags( DONT_DEALLOCATE_FLAG )
{
	
}

template <typename T>
AE_FORCEINLINE AETBaseArray<T>::AETBaseArray( T *lpBuffer, AEINT32 nSize, AEINT32 nCapacity ) :	m_lpData( lpBuffer ),
		m_nSize( nSize ),
		m_nCapacityAndFlags( nCapacity | DONT_DEALLOCATE_FLAG )
{
	AEASSERT_MSG( nSize >= 0 && nCapacity >= 0, "Array size and capacity must be non-negative." );
	AEASSERT_MSG( nSize <= nCapacity, "The size must be consistent with the capacity as memory will not be allocated in constructor. Use 'setSize' to allocate memory if inplace capacity too small.");
}

template <typename T>
AE_FORCEINLINE AETBaseArray<T>::~AETBaseArray( void )
{
	AEASSERT_MSG( AEIsPodType<T>::result || m_nSize==0, "Non-POD array elements not destructed");
	AEASSERT_MSG( m_nCapacityAndFlags & DONT_DEALLOCATE_FLAG, "Array memory not freed");
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::_setData( T *lpPtr, AEINT32 nSize, AEINT32 nCapacityAndFlags, AETypeIsPod )
{
	AETBaseArray<T>::SetDataUnchecked( lpPtr, nSize, nCapacityAndFlags );
}

template <typename T>
AE_FORCEINLINE AETBaseArray<T>& AETBaseArray<T>::_copyFromArray( AEMemoryAllocator &refAlloc, const AETBaseArray<T> &refSrc, AETypeIsPod )
{
	if( GetCapacity() < refSrc.GetSize() )
	{
		if( ( m_nCapacityAndFlags & DONT_DEALLOCATE_FLAG ) == 0 )
			refAlloc.TBufFree<T>( m_lpData, GetCapacity() );

		AEINT32 nSize = refSrc.GetSize();
		m_lpData = refAlloc.TBufAlloc<T>(nSize);
		m_nCapacityAndFlags = nSize;
	}

	m_nSize = refSrc.GetSize();
	Copy( m_lpData, refSrc.m_lpData, m_nSize );
	return *this;
}

template <typename T>
AE_FORCEINLINE AETBaseArray<T>& AETBaseArray<T>::_copyFromArray( AEMemoryAllocator &refAlloc, const AETBaseArray<T> &refSrc, AETypeIsClass )
{
	AEINT32 nOldSize = m_nSize;
	AEINT32 nNewSize = refSrc.GetSize();
	AEINT32 nCopiedSize = nNewSize > nOldSize ? nOldSize : nNewSize;

	Reserve( refAlloc, nNewSize ); // ensure space
	AEArrayUtil::Destruct( m_lpData + nNewSize, nOldSize - nNewSize, AETypeIsClass() ); // destruct items past the size of a, if any
	Copy( m_lpData, refSrc.m_lpData, nCopiedSize ); // copy objects into the 'live' part of this array
	AEArrayUtil::ConstructWithArray( m_lpData + nCopiedSize, nNewSize - nCopiedSize, refSrc.m_lpData + nCopiedSize, AETypeIsClass() ); // and construct the rest
	m_nSize = nNewSize;
	return *this;
}

template <typename T>
AE_FORCEINLINE T &AETBaseArray<T>::operator[] ( AEINT32 nIdx )
{
	AEASSERT( nIdx >= 0 && nIdx < m_nSize );
	return m_lpData[nIdx];
}

template <typename T>
AE_FORCEINLINE const T &AETBaseArray<T>::operator[] ( AEINT32 nIdx ) const
{
	AEASSERT( nIdx >= 0 && nIdx < m_nSize );
	return m_lpData[nIdx];
}

template <typename T>
AE_FORCEINLINE T &AETBaseArray<T>::Back( void )
{
	AEASSERT( m_nSize );
	return m_lpData[ m_nSize - 1 ];
}

template <typename T>
AE_FORCEINLINE const T &AETBaseArray<T>::Back( void ) const
{
	AEASSERT( m_nSize );
	return m_lpData[ m_nSize - 1 ];

}

template <typename T>
AE_FORCEINLINE AEINT32 AETBaseArray<T>::GetSize( void ) const
{
	return m_nSize;
}

template <typename T>
AE_FORCEINLINE AEINT32 AETBaseArray<T>::GetCapacity( void ) const
{
	return ( m_nCapacityAndFlags & static_cast<AEINT32>( CAPACITY_MASK ) );
}

template <typename T>
AE_FORCEINLINE AEBOOL32 AETBaseArray<T>::IsEmpty( void ) const
{
	return m_nSize == 0;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::Clear( void )
{
	// TODO
	AEArrayUtil::Destruct( m_lpData, m_nSize, typename AEIsPodType<T>::type() );
	m_nSize = 0;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::ClearAndDeallocate( AEMemoryAllocator &refAlloc )
{
	Clear();
	if( ( m_nCapacityAndFlags & DONT_DEALLOCATE_FLAG ) == 0 )
	{
		refAlloc.TBufFree<T>( const_cast<typename AERemoveConst<T>::type*>(m_lpData), GetCapacity() ); // this is needed since hkArray<const struct> is allowed
	}
	m_lpData = 0L;
	m_nCapacityAndFlags = DONT_DEALLOCATE_FLAG;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::OptimizeCapacity( AEMemoryAllocator &refAlloc, AEINT32 nNumFreeElemsLeft, AEBOOL32 bShrinkExact )
{
	AEINT32 nTotalCapacity = AE_COMPUTE_OPTIMIZED_CAPACITY( m_nSize, nNumFreeElemsLeft, bShrinkExact );
	
	if( nTotalCapacity < GetCapacity() )
		AEArrayUtil::Reduce( refAlloc, this, sizeof(T), 0L, nTotalCapacity );
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::RemoveAt( AEINT32 nIndex )
{
	AEASSERT( nIndex >= 0 && nIndex < m_nSize );

	AEArrayUtil::Destruct( &m_lpData[nIndex], 1, typename AEIsPodType<T>::type() );
	m_nSize--;
	
	// TODO
	//if( m_nSize != nIndex )
		//hkMemUtil::memCpyOneAligned<sizeof(T), HK_ALIGN_OF(T)>( m_lpData + nIndex, m_lpData + m_nSize );
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::RemoveAtAndCopy( AEINT32 nIndex )
{
	AEASSERT( nIndex >= 0 && nIndex < m_nSize );

	AEArrayUtil::Destruct( &m_lpData[nIndex], 1, typename AEIsPodType<T>::type() );
	m_nSize--;
	
	//TODO
	//AEMemUtil::memCpy<HK_ALIGN_OF(T)>( m_lpData + nIndex, m_lpData + nIndex + 1, ( m_nSize-nIndex )*sizeof(T) );
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::RemoveAtAndCopy( AEINT32 nIndex, AEINT32 nNumToRemove )
{
	AEASSERT( nNumToRemove > 0 );
	AEASSERT( nIndex >= 0 && ( ( nIndex + nNumToRemove ) <= m_nSize ) );

	AEArrayUtil::Destruct( m_lpData + nIndex, nNumToRemove, typename AEIsPodType<T>::type() );
	m_nSize -= nNumToRemove;
	
	//TODO
	//AEMemUtil::memCpy<HK_ALIGN_OF(T)>( m_lpData + nIndex, m_lpData + nIndex + nNumToRemove, (m_nSize-nIndex)*sizeof(T) );
}

template <typename T>
AE_FORCEINLINE AEINT32 AETBaseArray<T>::IndexOf( const T &tElem, AEINT32 nStart, AEINT32 nEnd ) const
{
	if( nEnd < 0 )
		nEnd = m_nSize;

	for(AEINT32 i = nStart; i < nEnd; ++i)
	{
		if( m_lpData[i] == tElem )
			return i;
	}

	return -1;
}

template <typename T>
AE_FORCEINLINE AEINT32 AETBaseArray<T>::LastIndexOf( const T &tElem ) const
{
	for(AEINT32 i = m_nSize-1; i >=0; --i)
	{
		if( m_lpData[i] == tElem )
			return i;
	}

	return -1;	
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::PopBack( AEINT32 nNumElemsToRemove )
{
	AEASSERT( m_nSize >= nNumElemsToRemove );
	AEArrayUtil::Destruct( m_lpData + m_nSize - nNumElemsToRemove, nNumElemsToRemove, typename AEIsPodType<T>::type() );
	m_nSize -= nNumElemsToRemove;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::PushBack( AEMemoryAllocator &refAlloc, const T &tElem )
{
	if( m_nSize == GetCapacity() )
	{
		AEASSERT_MSG( ! ( ( &tElem >= m_lpData ) && ( &tElem < (m_lpData + m_nSize) ) ), "AETBaseArray::pushBack can't push back element of same array during resize" );
		AEArrayUtil::ReserveMore( refAlloc, this, sizeof(T) );
	}

	AEArrayUtil::ConstructWithCopy<T>( m_lpData + m_nSize, 1, tElem, typename AEIsPodType<T>::type() );
	m_nSize++;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::PushBackUnchecked( const T &tElem )
{
	AEASSERT( m_nSize < GetCapacity() );
	AEArrayUtil::ConstructWithCopy<T>( m_lpData + m_nSize, 1, tElem, typename AEIsPodType<T>::type() );
	m_nSize++;
}

template <typename T>
AE_FORCEINLINE AEUINT32 AETBaseArray<T>::Reserve( AEMemoryAllocator &refAlloc, AEINT32 nSize )
{
	const AEINT32 nCapacity = GetCapacity();
	
	if( nCapacity < nSize )
	{
		AEINT32 nCap2 = 2 * nCapacity;
		AEINT32 nNewSize = ( nSize < nCap2 ) ? nCap2 : nSize;
			
		return AEArrayUtil::Reserve( refAlloc, this, nNewSize, sizeof(T) );
	}

	return 0;
}

template <typename T>
AE_FORCEINLINE AEUINT32 AETBaseArray<T>::ReserveExactly( AEMemoryAllocator &refAlloc, AEINT32 nSize )
{
	if( GetCapacity() < nSize )
		return AEArrayUtil::Reserve( refAlloc, this, nSize, sizeof(T) );

	return 0;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetSize( AEMemoryAllocator &refAlloc, AEINT32 nSize )
{
	Reserve( refAlloc, nSize );
	AEArrayUtil::Destruct( m_lpData + nSize, m_nSize - nSize, typename AEIsPodType<T>::type() );
	AEArrayUtil::Construct( m_lpData + m_nSize, nSize - m_nSize, typename AEIsPodType<T>::type() );
	m_nSize = nSize;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetSize( AEMemoryAllocator &refAlloc, AEINT32 nSize, const T &tFill )
{
	Reserve( refAlloc, nSize );
	AEArrayUtil::Destruct( m_lpData + nSize, m_nSize - nSize, typename AEIsPodType<T>::type() );
	AEArrayUtil::ConstructWithCopy( m_lpData + m_nSize, nSize - m_nSize, tFill, typename AEIsPodType<T>::type() );
	m_nSize = nSize;
}

template <typename T>
AE_FORCEINLINE AEUINT32 AETBaseArray<T>::TrySetSize( AEMemoryAllocator &refAlloc, AEINT32 nSize )
{
	AEUINT32 uiRes = Reserve( refAlloc, nSize );
	if( uiRes == 0 )
	{
		AEArrayUtil::Destruct( m_lpData + nSize, m_nSize - nSize, typename AEIsPodType<T>::type() );
		AEArrayUtil::Construct( m_lpData + m_nSize, nSize - m_nSize, typename AEIsPodType<T>::type() );
		m_nSize = nSize;
	}

	return uiRes;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetSizeUnchecked( AEINT32 nSize )
{
	AEASSERT( nSize <= GetCapacity() );
	AEArrayUtil::Destruct( m_lpData + nSize, m_nSize - nSize, typename AEIsPodType<T>::type() );
	AEArrayUtil::Construct( m_lpData + m_nSize, nSize - m_nSize, typename AEIsPodType<T>::type() );
	m_nSize = nSize;
}

template <typename T>
AE_FORCEINLINE T &AETBaseArray<T>::ExpandOne( AEMemoryAllocator &refAlloc )
{
	if( m_nSize == GetCapacity() )
	{
		AEArrayUtil::ReserveMore( refAlloc, this, sizeof(T) );
	}
	AEArrayUtil::Construct( m_lpData + m_nSize, 1, typename AEIsPodType<T>::type() );
	return m_lpData[m_nSize++];
}

template <typename T>
AE_FORCEINLINE T *AETBaseArray<T>::ExpandBy( AEMemoryAllocator &refAlloc, AEINT32 nSize )
{
	AEINT32 nOldsize = m_nSize;
	Reserve( refAlloc, m_nSize + nSize );
	AEArrayUtil::Construct( m_lpData + m_nSize, nSize, typename AEIsPodType<T>::type() );
	m_nSize += nSize;
	
	return m_lpData+nOldsize;
}

template <typename T>
AE_FORCEINLINE T *AETBaseArray<T>::ExpandByUnchecked( AEINT32 nSize )
{
	AEASSERT( nSize >= 0 && m_nSize+nSize <= GetCapacity() );
	AEINT32 nOldsize = m_nSize;
	m_nSize = nOldsize + nSize;
	AEArrayUtil::Construct(m_lpData + nOldsize, m_nSize - nOldsize, typename AEIsPodType<T>::type() );
	
	return m_lpData+nOldsize;
}

template <typename T>
AE_FORCEINLINE T *AETBaseArray<T>::ExpandAt( AEMemoryAllocator &refAlloc, AEINT32 nIndex, AEINT32 nNumToInsert )
{
	AEASSERT( nIndex >= 0 && nIndex <= m_nSize );

	const AEINT32 nNewSize = nNumToInsert + m_nSize;
	const AEINT32 nNumToMove = m_nSize - nIndex;
	
	if( nNewSize > GetCapacity() )
		Reserve( refAlloc, nNewSize );

	// TODO
	//AEMemUtil::memMove( m_lpData + nIndex + nNumToInsert, m_lpData + nIndex, nNumToMove*sizeof(T) );
	AEArrayUtil::Construct( m_lpData + nIndex, nNumToInsert, typename AEIsPodType<T>::type() );
	m_nSize = nNewSize;

	return m_lpData + nIndex;
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::RemoveAllAndCopy( const T &tElem )
{
	AEINT32 nDestination = 0;
	
	// Do no copying until we've found a t.
	while( ( nDestination < m_nSize ) && ( m_lpData[nDestination] != tElem ) )
		++nDestination;

	// If we have found a t, start copying.
	for( AEINT32 nSource = nDestination; nDestination < m_nSize; ++nSource )
	{
		if( m_lpData[nSource] != tElem )
		{
			// TODO
			//AEMemUtil::memCpyOneAligned<sizeof(T), HK_ALIGN_OF(T)>( m_lpData + nDestination, m_lpData + nSource );
			//++destination;
		}
		else
		{
			AEArrayUtil::Destruct( m_lpData + nSource, 1, typename AEIsPodType<T>::type() );
			--m_nSize;
		}
	}
}

template <typename T>
AE_FORCEINLINE AEBOOL32 AETBaseArray<T>::TryPushBack( const T &tElem )
{
	if( m_nSize < GetCapacity() )
	{
		AEArrayUtil::ConstructWithCopy( m_lpData + m_nSize, 1, tElem, typename AEIsPodType<T>::type() );
		m_nSize++;
		return true;
	}
	else
	{
		return false;
	}
}

template <typename T>
AE_FORCEINLINE typename AETBaseArray<T>::AEArrayIteratorPtr AETBaseArray<T>::Begin( void )
{
	return m_lpData;
}

template <typename T>
AE_FORCEINLINE typename AETBaseArray<T>::AEArrayIteratorPtr AETBaseArray<T>::End( void )
{
	return m_lpData + m_nSize;
}

template <typename T>
AE_FORCEINLINE typename AETBaseArray<T>::AEConstArrayIteratorPtr AETBaseArray<T>::Begin( void ) const
{
	return m_lpData;
}

template <typename T>
AE_FORCEINLINE typename AETBaseArray<T>::AEConstArrayIteratorPtr AETBaseArray<T>::End( void ) const
{
	return m_lpData + m_nSize;
}

template <typename T>
AE_FORCEINLINE void AE_CALL AETBaseArray<T>::Copy( T *lpDest, const T *lpSrc, AEINT32 nItems )
{
	AEASSERT( ( lpDest <= lpSrc ) || ( lpSrc+nItems <= lpDest ) );

	for( AEINT32 i = 0; i < nItems; ++i )
		lpDest[i] = lpSrc[i];
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetDataAutoFree( T *lpPtr, AEINT32 nSize, AEINT32 nCapacity )
{
	AEASSERT( nSize >= 0 );
	AEASSERT( nSize <= nCapacity );
	AETBaseArray<T>::SetData( lpPtr, nSize, nCapacity, typename AEIsPodType<T>::type() );
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetDataUserFree( T *lpPtr, AEINT32 nSize, AEINT32 nCapacity )
{
	AEASSERT( nSize >= 0 );
	AEASSERT( nSize <= nCapacity );
	AETBaseArray<T>::SetData( lpPtr, nSize, nCapacity | DONT_DEALLOCATE_FLAG, typename AEIsPodType<typename AERemoveConst<T>::type>::type() );
}

template <typename T>
AE_FORCEINLINE void AETBaseArray<T>::SetDataUnchecked( T *lpPtr, AEINT32 nSize, AEINT32 nCapacityAndFlags )
{
	m_lpData = lpPtr;
	m_nSize = nSize;
	m_nCapacityAndFlags = nCapacityAndFlags;
}

template <typename T>
AE_FORCEINLINE AEINT32 AETBaseArray<T>::GetCapacityAndFlags( void ) const
{
	return m_nCapacityAndFlags;
}

template <typename T>
void AETBaseArray<T>::Append( AEMemoryAllocator &refAlloc, const T *lpArray, AEINT32 nNumElems )
{
	const AEINT32 nNewsize = m_nSize + nNumElems;

	if( nNewsize > GetCapacity() )
		Reserve( refAlloc, nNewsize );

	AEArrayUtil::ConstructWithArray( m_lpData + m_nSize, nNumElems, lpArray, typename AEIsPodType<T>::type());
	m_nSize = nNewsize;
}
#endif // _AETBASEARRAY_INL_